package top.techqi.usbd.driver.serial

import android.hardware.usb.UsbConstants
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbDeviceConnection
import android.hardware.usb.UsbEndpoint
import android.hardware.usb.UsbRequest
import android.os.Build
import top.techqi.usbd.UsbDriver
import java.io.IOException
import java.nio.ByteBuffer
import java.util.EnumSet
import java.util.concurrent.TimeoutException
import kotlin.math.max
import kotlin.math.min

@Suppress("unused")
abstract class BasicsSerialDriver(device: UsbDevice, intfId: Int) :
    UsbDriver(device, intfId) {
    protected lateinit var conn: UsbDeviceConnection
    protected lateinit var usbRequest: UsbRequest

    protected lateinit var rEndpoint: UsbEndpoint
    protected lateinit var wEndpoint: UsbEndpoint

    abstract val supportedControlLines: EnumSet<ControlLine>

    /**
     * 载波检测(Carrier Detect)
     */
    @get:Throws(IOException::class)
    open val cd: Boolean
        get() = throw UnsupportedOperationException()

    /**
     * 振铃指示器(Ring Indicator)
     */
    @get:Throws(IOException::class)
    open val ri: Boolean
        get() = throw UnsupportedOperationException()

    /**
     * 清除发送缓冲区(Clear To Send)
     */
    @get:Throws(IOException::class)
    open val cts: Boolean
        get() = throw UnsupportedOperationException()

    /**
     * 数据集准备就绪(Data Set Ready)
     */
    @get:Throws(IOException::class)
    open val dsr: Boolean
        get() = throw UnsupportedOperationException()

    /**
     * 数据终端就绪(Data Terminal Ready)
     */
    @get:Throws(IOException::class)
    @set:Throws(IOException::class)
    open var dtr: Boolean
        get() = throw UnsupportedOperationException()
        set(_) = throw UnsupportedOperationException()

    /**
     * 请求发送(Request To Send)
     */
    @get:Throws(IOException::class)
    @set:Throws(IOException::class)
    open var rts: Boolean
        get() = throw UnsupportedOperationException()
        set(_) = throw UnsupportedOperationException()

    @Throws(IOException::class)
    override fun open(conn: UsbDeviceConnection) {
        this.conn = conn
        openInterfaces()
        if (!::rEndpoint.isInitialized || !::wEndpoint.isInitialized) {
            throw IOException("Could not get read & write endpoints")
        }
        usbRequest = UsbRequest()
        usbRequest.initialize(conn, rEndpoint)
    }

    @Throws(IOException::class)
    override fun close() {
        try {
            this.usbRequest.cancel()
        } catch (_: Exception) {
        }
        try {
            closeInterfaces()
        } catch (_: Exception) {
        }
        try {
            conn.close()
        } catch (_: Exception) {
        }
    }

    @Throws(IOException::class)
    protected abstract fun openInterfaces()
    protected abstract fun closeInterfaces()

    @Throws(IOException::class)
    abstract fun setParameters(baudRate: Int, dataBits: Byte, stopBits: Byte, parity: Byte)

    @Throws(IOException::class)
    open fun setBreak(value: Boolean) {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    protected fun testConnection(
        fullyCheck: Boolean, message: String? = "USB get_status request failed"
    ) {
        if (!this::conn.isInitialized || !this::usbRequest.isInitialized) {
            throw IOException("Connection closed")
        }

        if (fullyCheck) {
            val buffer = ByteArray(2)
            val length = conn.controlTransfer(
                UsbConstants.USB_DIR_IN or UsbConstants.USB_TYPE_STANDARD,
                0, 0, 0,
                buffer, buffer.size, 200
            )
            if (length < 0) throw IOException(message)
        }
    }

    @Throws(IOException::class)
    fun read(dst: ByteArray, size: Int, wait: Int): Int {
        val nRead: Int = if (wait != 0) {
            conn.bulkTransfer(
                rEndpoint, dst,
                arrayOf(size, dst.size, MAX_READ_SIZE).min(),
                wait
            )
        } else {
            val buffer = ByteBuffer.wrap(dst, 0, size)
            if (
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    !usbRequest.queue(buffer)
                } else {
                    @Suppress("DEPRECATION")
                    !usbRequest.queue(buffer, size)
                }
            ) {
                throw IOException("Queueing USB request failed")
            }
            if (conn.requestWait() == null) {
                throw IOException("Waiting for USB request failed")
            }
            buffer.position()
        }
        if (nRead <= 0) {
            testConnection(true)
        }
        return max(nRead, 0)
    }

    fun write(src: ByteArray, size: Int, wait: Int) {
        val startTime = System.currentTimeMillis()

        var elapse = 0
        var offset = 0
        val buffer = ByteArray(wEndpoint.maxPacketSize)
        while (offset < size) {
            val timeout = if (wait == 0) 0 else wait - elapse
            val length = min(size - offset, buffer.size)

            val nWrite = if (wait != 0 && timeout <= 0) -2
            else {
                System.arraycopy(src, offset, buffer, 0, length)
                conn.bulkTransfer(wEndpoint, buffer, length, timeout)
            }
            elapse = (System.currentTimeMillis() - startTime).toInt()

            if (nWrite <= 0) {
                val message = "Write $length bytes" +
                        " at $offset of totally ${src.size} bytes" +
                        " after $elapse milliseconds failed"

                if (wait != 0) {
                    testConnection(elapse < wait, message)
                    throw TimeoutException(message)
                } else {
                    throw IOException(message)
                }
            }
            offset += nWrite
        }
    }

    enum class ControlLine { RTS, CTS, DTR, DSR, CD, RI }

    companion object {
        private const val MAX_READ_SIZE = 16 * 1024 // = old bulkTransfer limit

        const val DATABITS_5: Byte = 5
        const val DATABITS_6: Byte = 6
        const val DATABITS_7: Byte = 7
        const val DATABITS_8: Byte = 8

        const val STOPBITS_1: Byte = 1
        const val STOPBITS_2: Byte = 2

        const val PARITY_NONE: Byte = 0
        const val PARITY_ODD: Byte = 1
        const val PARITY_EVEN: Byte = 2
        const val PARITY_MARK: Byte = 3
        const val PARITY_SPACE: Byte = 4
    }
}
